from django.core.exceptions              import ObjectDoesNotExist,ImproperlyConfigured
from django.db                           import models,transaction
from django.utils.translation            import ugettext_lazy as _
from sitepal.sitebase                    import fields
from sitepal.sitebase.conf               import properties
from sitepal.sitebase.managers           import ResourceManager
from sitepal.sitebase.models             import Resource
from sitepal.sitebase.util               import iterappmodules
from sitepal.sitetheme                   import *
from sitepal.sitetheme.models.blocks     import Block
from sitepal.sitetheme.models.containers import GRID_CUSTOM_CHOICES,GRID_CLEAR_CHOICES
from sitepal.sitetheme.models.containers import GRID_POSITION_CHOICES,GRID_VISIBILITY_CHOICES
from sitepal.sitetheme.models.regions    import Region
from sitepal.sitetheme.models.util       import validate_grid_overrides

GRID_OVERRIDE_NAMES = frozenset(('weight','grid_custom','grid_clear','grid_enabled','grid_pages',
                                 'grid_position','grid_prefix','grid_suffix','grid_weight',
                                 'grid_width','grid_visibility'))
GRID_PROXY_NAMES    = frozenset(('name','title','description','display_title','style','source',
                                 'render_class','display_classes','block_type','renderer_name',
                                 'is_visible'))

class LayoutManager(ResourceManager):
    _layout_cache   = {}
    _current_layout = None

    def get_by_name(self,name):
        if name == CURRENT_LAYOUT_NAME:
            name = properties.get('site-layout-current',admin=True)
        try:
            layout = self._layout_cache[name]
        except KeyError:
            layout                   = self.get(name__exact=name)
            self._layout_cache[name] = layout
        return layout

    def get_current(self):
        if not self._current_layout:
            self._current_layout = self.get_by_name(properties.get('site-layout-current',
                                                                   admin=True))
        return self._current_layout

    def set_current(self,name):
        self._current_layout = self.get_by_name(name)

    def itermodules(self):
        return iterappmodules(submodule='siteapp.layouts')

    def sync_overrides(self,container,data):
        overrides = {}
        for name in GRID_OVERRIDE_NAMES:
            if name in data:
                overrides[name] = data[name]
        container.overrides = overrides

    @transaction.commit_on_success
    def sync_regions(self,layout,data):
        if 'regions' in data:
            for name,values in data['regions'].iteritems():
                try:
                    region = Region.objects.get(name__exact=name)
                    try:
                        obj = layout.layoutregion_set.get(region=region)
                    except ObjectDoesNotExist:
                        obj        = LayoutRegion()
                        obj.layout = layout
                        obj.region = region
                    self.sync_overrides(obj,values)
                    obj.full_clean()
                    obj.save()
                except Exception,e:
                    raise ImproperlyConfigured,e

    @transaction.commit_on_success
    def sync_blocks(self,layout,data):
        if 'blocks' in data:
            for name,values in data['blocks'].iteritems():
                try:
                    block = Block.objects.get(name__exact=name)
                    try:
                        obj = layout.layoutblock_set.get(block=block)
                    except ObjectDoesNotExist:
                        obj        = LayoutBlock()
                        obj.layout = layout
                        obj.block  = block
                    if 'region' in values:
                        obj.region = layout.layoutregion_set.get(region__name=values['region'])
                    self.sync_overrides(obj,values)
                    obj.full_clean()
                    obj.save()
                except Exception,e:
                    raise ImproperlyConfigured,e

    @transaction.commit_on_success
    def sync_object(self,name,data):
        created,obj = super(LayoutManager,self).sync_object(name,data)
        try:
            obj.title       = data['title']
            obj.description = data.get('description')
            obj.full_clean()
            obj.save()
            self.sync_regions(obj,data)
            self.sync_blocks(obj,data)
            return created,obj
        except Exception,e:
            raise ImproperlyConfigured,e

class Layout(Resource):
    title       = models.CharField(_("title"),editable=False,max_length=100)
    description = models.TextField(_("description"),max_length=2048,editable=False,
                                   blank=True,null=True)
    objects     = LayoutManager()

    class Meta(Resource.Meta):
        verbose_name        = _('layout')
        verbose_name_plural = _('layouts')
        unique_together     = ('site','name')
        app_label           = 'sitetheme'

    @property
    def regions(self):
        regions = list(self.layoutregion_set.all())
        regions.sort(key=lambda x: (x.grid_weight,x.weight,x.name))
        return tuple(regions)

    @property
    def blocks(self):
        blocks = list(self.layoutblock_set.all())
        blocks.sort(key=lambda x: x.name.to_lower())
        return tuple(blocks)

class LayoutContainer(models.Model):
    layout    = models.ForeignKey(Layout,verbose_name=_("layout"),related_name="%(class)s_set")
    overrides = fields.JSONDictField(_("overrides"),max_length=2048,default={},blank=True,
                                     validators=[validate_grid_overrides])

    class Meta:
        verbose_name        = _('layout container')
        verbose_name_plural = _('layout containers')
        app_label           = 'sitetheme'
        abstract            = True

    def __unicode__(self):
        return self.container.name

    def __getattr__(self,name):
        if name in GRID_OVERRIDE_NAMES:
            try:
                return self.overrides[name]
            except Exception,e:
                return getattr(self.container,name)
        if name in GRID_PROXY_NAMES:
            return getattr(self.container,name)
        raise AttributeError,"{0} object has no attribute {1}".format(type(self).__name__,name)

    @property
    def container(self):
        raise NotImplementedError

    @property
    def grid_classes(self):
        classes = []
        classes.append('grid_{0}'.format(self.grid_width))
        if self.grid_position:
            classes.append(self.grid_position)
        if self.grid_prefix:
            classes.append(self.grid_prefix)
        if self.grid_suffix:
            classes.append(self.grid_suffix)
        if self.grid_weight < 0:
            classes.append('pull_{0}'.format(self.grid_weight))
        if self.grid_weight > 0:
            classes.append('push_{0}'.format(self.grid_weight))
        return tuple(classes)

    @property
    def attributes(self):
        attrs = self.container.attributes
        if 'class' in attrs:
            attrs['class'] = "{0} {1}".format(attrs['class'],u' '.join(self.grid_classes))
        else:
            attrs['class'] = u' '.join(self.grid_classes)
        return attrs

    def get_renderer(self,theme):
        return self.render_class(theme,self)

    def render_clear(self,theme):
        return theme.render_tag('div',attributes={'class':'clear'},allow_empty=True)

    def render(self,theme,request):
        renderer = self.get_renderer(theme)
        theme    = renderer.theme
        content  = renderer.render(request)
        if content:
            output = []
            if self.grid_clear in ('before','both'):
                output.append(self.render_clear(theme))
            output.append(theme.escape(content))
            if self.grid_clear in ('after','both'):
                output.append(self.render_clear(theme))
            return theme.safe(u''.join(output))
        return u''

class LayoutRegion(LayoutContainer):
    region = models.ForeignKey(Region,verbose_name=_("region"))

    class Meta(LayoutContainer.Meta):
        verbose_name        = _('layout region')
        verbose_name_plural = _('layout regions')
        unique_together     = ('layout','region')
        app_label           = 'sitetheme'

    @property
    def blocks(self):
        blocks = list(self.layoutblock_set.all())
        blocks.sort(key=lambda x: (x.grid_weight,x.weight,x.name))
        return tuple(blocks)

    @property
    def container(self):
        return self.region

class LayoutBlock(LayoutContainer):
    region = models.ForeignKey(LayoutRegion,verbose_name=_("region"),blank=True,null=True)
    block  = models.ForeignKey(Block,verbose_name=_("block"))

    class Meta(LayoutContainer.Meta):
        verbose_name        = _('layout block')
        verbose_name_plural = _('layout blocks')
        unique_together     = ('layout','block')
        app_label           = 'sitetheme'

    @property
    def container(self):
        return getattr(self.block,self.block.block_type)

